/**
 *
 * @file http_server.c
 *
 * @brief HTTP SERVER
 *
 * $Id: $
 */

/*
 * This file is part of Tsar.
 *
 * Tsar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tsar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tsar.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <http_server.h>
#include <gen_html_report.h>
#include <output.h>
#include <utils.h>
#include <parser.h>

/* A part of HTTP STATUS CODES specified in RFC 2616  */
enum {
    HTTP_100_CONTINUE           = 100,
    HTTP_200_OK                 = 200,
    HTTP_400_BAD_REQUEST        = 400,
    HTTP_403_FORBIDDEN          = 403,
    HTTP_404_NOT_FOUND          = 404,
    HTTP_405_METH_NOT_ALLOWED   = 405,
    HTTP_411_LENGTH_REQUIRED    = 411,
    HTTP_414_TOO_LARGE          = 414,
    HTTP_415_UNSUP_MEDIA_TYPE   = 415,
    HTTP_500_INTERNAL_ERROR     = 500,
    HTTP_501_NOT_IMPLEMENTED    = 501,
    HTTP_505_UNSUP_VERSION      = 505
};

httpStatus_t HttpStatusCodes[] = {
    { HTTP_100_CONTINUE,            "Continue" },
    { HTTP_200_OK,                  "OK" },
    { HTTP_400_BAD_REQUEST,         "Bad request" },
    { HTTP_403_FORBIDDEN,           "Forbidden" },
    { HTTP_404_NOT_FOUND,           "Not Found" },
    { HTTP_405_METH_NOT_ALLOWED,    "Method Not Allowed" },
    { HTTP_411_LENGTH_REQUIRED,     "Length Required" },
    { HTTP_414_TOO_LARGE,           "Request-URI Too Large" },
    { HTTP_415_UNSUP_MEDIA_TYPE,    "Unsupported Media Type" },
    { HTTP_500_INTERNAL_ERROR,      "Internal Server Error" },
    { HTTP_501_NOT_IMPLEMENTED,     "Not Implemented" },
    { HTTP_505_UNSUP_VERSION,       "HTTP Version not supported" }, 
    { 0,    NULL } 
};


#if defined( TEST_HTTP )
/*
    make
   rm main.o
   gcc  -Wall -pedantic -g -pipe  -I./includes -I/usr/include -I/usr/local/include -I./nopcap -D_GNU_SOURCE -DIPV4_ONLY -DTEST_HTTP -D_FORTIFY_SOURCE=2 -c  http_server.c -o http_server.o
   gcc -o tsar_test options.o utils.o tsar.o output.o sniffer.o webserver.o sockpool.o parser.o http_server.o gen_html_report.o  -L/usr/lib  nopcap/libpcap.a -lm
   ./tsar_test
*/

int main( int argc, char **argv ) {

    http_t * pHandler;
   
    char http_header_client1[] = "POST /bar.gif HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length : 17\r\nparam=pcap coucou\r\n";
    char http_header_client2[] = "POST /line.gif HTTP/1.1\r\nConnection: keep-alive\r\n";
    char http_header_client3[] = "GET /line.gif HTTP/1.1\r\nConnection: keep-alive\r\n";
    char http_header_client4[] = "GET /line.gif HTTP/1.1\r\nConnection: keep-alive\r\n";
    char http_header_client5[] = "IUZGIFG7386YRH3IURGH3IUGGIGIUFG3IFU";
    char http_header_client6[] = "GET /hseoisehgosi HTTP/1.1\r\nConnection: keep-alive\r\n";
    char http_header_client7[] = "GET /style.css HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
    char http_header_client8[] = "GET / HTTP/1.1";

    pHandler = http_server_reply( http_header_client7, strlen( http_header_client7 ) );

    debug( " %s ! \n", pHandler->serverResponse ); 
    debug( " REPLY SIZE : %lu ! \n", pHandler->replyLen ); 

    http_destroy_handle( pHandler );
pHandler = http_server_reply( http_header_client5, strlen( http_header_client5 ) );

    debug( " %s ! \n", pHandler->serverResponse ); 
    debug( " REPLY SIZE : %lu ! \n", pHandler->replyLen ); 

    http_destroy_handle( pHandler );
pHandler = http_server_reply( http_header_client3, strlen( http_header_client3 ) );

    debug( " %s ! \n", pHandler->serverResponse ); 
    debug( " REPLY SIZE : %lu ! \n", pHandler->replyLen ); 

    http_destroy_handle( pHandler );

    return 0;
}

#endif

/* ********** EXTERNAL FUNCTIONS ************** */

http_t * http_server_reply( char * httpHeader, ssize_t len, char * pcapFilename )
{
    /* Main function using all the others excepting shutdown_http_handler,
    see the header file to view functions utilities */
    http_t * handler = NULL;

    handler = init_http_handler();
    if ( receipt_http_header( httpHeader, len, handler ) )
    {
        print_error( "Error while processing client's http request\n" );
        http_destroy_handle( handler );
        handler = NULL;
    }
    else if ( craft_reply_header( handler, pcapFilename ) )
    {
        print_error( "Error while crafting server's http reply\n" );
        http_destroy_handle( handler );
        handler = NULL;
    }
    else
    {
        /* Success case */
        finalize_http_reply( handler );
    }

    return handler;
}


void http_destroy_handle( http_t * httpHandler )
{
    /* Free free(1) if not NULL */

    Free( httpHandler->clientRequest );

    Free( httpHandler->serverResponse );

    Free( httpHandler->requestedFile );

    Free( httpHandler->entireResponse );

    Free( httpHandler->path );

    Free( httpHandler->paramLine );

    Free( httpHandler->mimeType );
    httpHandler->method         =   NULL;
    httpHandler->path           =   NULL;
    httpHandler->protocol       =   NULL;

    Free( httpHandler );

    print( MESSAGE_INFO, "HTTP Handler destroyed\n" );
}


/* ********** INTERNAL FUNCTIONS ************** */

http_t* init_http_handler( void )
{
    http_t * httpHandler = NULL;

    httpHandler = Malloc( sizeof( http_t ) );

    httpHandler->clientRequest  =   NULL;
    httpHandler->serverResponse =   NULL; 
    httpHandler->requestedFile  =   NULL;
    httpHandler->entireResponse =   NULL;

    httpHandler->headerSize     =   0;
        
    httpHandler->contentLength  =   0;
    httpHandler->replyLen       =   0;

    httpHandler->method         =   NULL;
    httpHandler->path           =   NULL;
    httpHandler->protocol       =   NULL;

    httpHandler->isAuthorized   =   false;
    
    /* Initialized at Internal error */ 
    httpHandler->numberCode     =   HTTP_500_INTERNAL_ERROR;
    httpHandler->returnCode     =   NULL;

    httpHandler->mimeType       =   NULL;
    httpHandler->paramLine      =   NULL;

    print( MESSAGE_INFO, "Http handler succefully initialized\n" );

    return httpHandler;
}

int receipt_http_header( char* httpHeader, ssize_t clientHeaderSize, http_t* httpHandler )
{
    /* Check if the length is long enought*/ 
    if ( clientHeaderSize < MIN_HTTP_EXPECTED_SIZE )
    {
        print( MESSAGE_CRITICAL, "Client's request too short (%d bytes), dropped!\n", clientHeaderSize );
        return 1;
    }

    /*Copy client header in the handler */
    httpHandler->clientRequest = Malloc( clientHeaderSize + 1);
    memcpy( httpHandler->clientRequest, httpHeader, clientHeaderSize );
    httpHandler->clientRequest[clientHeaderSize] = '\0';

    /* Switch if function of method used */
    switch ( get_method_path_protocol( httpHeader, httpHandler ) )
    {
        case    HTTP_METHOD_GET :
            do_GET_reply( httpHandler );
            break;

        case    HTTP_METHOD_POST  :
            do_POST_reply( httpHandler );
            break;

        case    BAD_REQUEST  :
			debug ( "[HTTP_SERVER] Bad Request\n" );
            set_status_code( httpHandler, HTTP_400_BAD_REQUEST );
            break;

        default :
            /* METHOD NOT ALLOWED */
			debug ( "[HTTP_SERVER] Method not allowed\n" );
            set_status_code( httpHandler, HTTP_405_METH_NOT_ALLOWED );
            break;
    }
    check_path( httpHandler );

    debug( "[HTTP_SERVER] Request :\n\tMethod : %s\n\tPath : %s\n\tProtocol : %s\n", httpHandler->method, httpHandler->path, httpHandler->protocol );

    return 0;
}

void check_path ( http_t* httpHandler )
{
    FILE * streamTest;

    debug( "[HTTP_SERVER] path : %s\n", httpHandler->path );

    if ( httpHandler->path == NULL )
    {
        httpHandler->isAuthorized = false;
        set_status_code( httpHandler, HTTP_400_BAD_REQUEST );
        httpHandler->path = Malloc( strlen( "/error.html" ) + 1 );
        strcpy( httpHandler->path, "/error.html" );
    }
    else if ( strstr(  httpHandler->path, ".." ) )
    {
        httpHandler->isAuthorized = false;
        set_status_code( httpHandler, HTTP_400_BAD_REQUEST );
        httpHandler->path = Malloc( strlen( "/error.html" ) + 1 );
        strcpy( httpHandler->path, "/error.html" );
    }
    else
    { 
        streamTest = Fopen_HTTP( httpHandler->path, "r" );

        if ( streamTest != NULL )
        {
            httpHandler->isAuthorized = true;
            print ( MESSAGE_NOTICE, "[HTTP_SERVER] Warning : You are authorized to access this file ! \n" );   
            fclose( streamTest ); 
        }    
        else if ( strcmp ( httpHandler->path, "/index.html" ) == 0 )
        {
            httpHandler->isAuthorized = true;
            httpHandler->path = Malloc( strlen( "/index.html" ) + 1 );
            strcpy( httpHandler->path, "/index.html" );
            print ( MESSAGE_NOTICE, "[HTTP_SERVER] You are authorized to access index file ! \n" );       
        }  
        else 
        {
            httpHandler->isAuthorized = false;
            set_status_code( httpHandler, HTTP_404_NOT_FOUND );
            httpHandler->path = Malloc( strlen( "/error.html" ) + 1 );
            strcpy( httpHandler->path, "/error.html" );
        }
    }
}

int get_method_path_protocol( char * httpHeader, http_t * httpHandler )
{
    char * firstLine, * rawPath;
    int retVal;

    firstLine = strtok( httpHeader, "\r\n" );

    httpHandler->method   = strtok( firstLine, " " );
    rawPath               = strtok( NULL, " " );
    httpHandler->protocol = strtok( NULL, "\0" );

    if ( !httpHandler->method || !rawPath || !httpHandler->protocol )
    {
        return BAD_REQUEST;
    }
    else if ( strcmp( rawPath, "/" ) == 0 )
    {
        httpHandler->path = Malloc( strlen( "/index.html" ) + 1 );
        strcpy( httpHandler->path, "/index.html" );
    }
    else
    {
        httpHandler->path = Malloc( strlen( rawPath ) + 1 );
        strcpy( httpHandler->path, rawPath );
    }

    if ( strcmp( httpHandler->method, "GET" ) == 0 )
    {
        retVal = HTTP_METHOD_GET;
    }
    else if ( strcmp( httpHandler->method, "POST" ) == 0 )
    {
        retVal = HTTP_METHOD_POST;
    }
    else
    {
        retVal = HTTP_METHOD_INVALID;
    }

    return retVal;
}

int craft_reply_header ( http_t* httpHandler, char * pcapFilename )
{
    char responseArray[4096] = "", date[30] = "";
    char * memPtr = NULL;
    time_t intps; 
    struct tm * p_dateheure;
    int errorType=PCAP_NO_ERROR;

    if ( !strcmp( httpHandler->path, "/"REPORT_FILENAME ) )
    {
        printf( "[HTTP_SERVER] Requested path is %s, now rebuilding index.html using pcap file %s\n", httpHandler->path, pcapFilename );
        errorType = ( create_html_page( httpHandler, pcapFilename ) );

        if ( errorType != PCAP_NO_ERROR )
        { 
            /* re-create index.html with error page and error type */
            if ( load_error ( errorType ) )
            {
                print_error( "[HTTP_SERVER] Critical error : Cannot print error page\n" );
            }
        }
    }

    /* Construct time using asctime() */
    intps = time( NULL ); 
    p_dateheure = localtime( &intps ); 
    strcpy ( date, asctime( p_dateheure ) );
    date[ strlen( date ) - 2 ] = '\0';
    date[ strlen( date ) - 1 ] = '\0';
    
    /* Taking file length and copy into the handler,requestedFile buffer*/
    httpHandler->requestedFile = ( char * )load_file( httpHandler->path, &( httpHandler->contentLength ) );

    debug ( "[HTTP_SERVER] Content Length %lu \n", httpHandler->contentLength );

    /* Set the mime Type */
    get_mime_type ( httpHandler );

    memPtr = responseArray;
    memPtr += sprintf( responseArray, "HTTP/1.x %d %s\r\n", httpHandler->numberCode, httpHandler->returnCode );
    memPtr += sprintf( memPtr, "Date: %s\r\n", date );
    memPtr += sprintf( memPtr, "Content-Type: %s\r\n", httpHandler->mimeType );
    memPtr += sprintf( memPtr, "Content-Length: %lu\r\n", httpHandler->contentLength );
    memPtr += sprintf( memPtr, "Server: Tsar HTTP Server\r\n\r\n");
    *( memPtr + 1 ) = '\0';
    

    /* Copying array in the handler */
    httpHandler->headerSize = strlen( responseArray );
    httpHandler->serverResponse = Malloc( httpHandler->headerSize * sizeof( char ) + 1 );
    strncpy( httpHandler->serverResponse, responseArray, httpHandler->headerSize  );
    httpHandler->serverResponse[httpHandler->headerSize]='\0';

    return 0;
}


void finalize_http_reply( http_t * httpHandler )
{
    httpHandler->replyLen =  httpHandler->headerSize + httpHandler->contentLength;
    httpHandler->entireResponse = Malloc( ( httpHandler->replyLen +1 ) * sizeof( char ) );
    
    memcpy( httpHandler->entireResponse, 
            httpHandler->serverResponse, 
            httpHandler->headerSize * sizeof ( char ) );
    memcpy( httpHandler->entireResponse+( httpHandler->headerSize ), 
            httpHandler->requestedFile, 
            httpHandler->contentLength * sizeof ( char ) );

    debug( "[HTTP_SERVER] POST PARAMLINE : %s\n", httpHandler->paramLine );

}


void do_POST_reply ( http_t* httpHandler )
{
    /* */
    char * p;

    p = strtok( httpHandler->clientRequest, "\n");
    while( p != NULL )
    {
        if ( strncmp( p, "param", 5 ) == 0 )
        {
            give_params_value( p, httpHandler );
            break;
        }
        else
        {
            p = strtok( NULL, "\n" );
        }
    }
}

void do_GET_reply ( http_t* httpHandler )
{
    /* Nothing to do */
}

void give_params_value ( char* headerLine, http_t* httpHandler )
{
    char* paramLine;

    paramLine = strchr( headerLine, (int) '=');
    paramLine++;
    
    httpHandler->paramLine = ( char * )Malloc( strlen( url_decode( paramLine ) ) + 1 );
    strcpy( httpHandler->paramLine, paramLine );
}


void get_mime_type ( http_t* httpHandler )
{
    char* extension;

    httpHandler->mimeType = Malloc( sizeof( char ) * 30 );
    httpHandler->mimeType[0] = '\0';

    extension = strchr( httpHandler->path, '.' );
    if ( extension == NULL )
    {
        set_status_code( httpHandler, HTTP_415_UNSUP_MEDIA_TYPE );
        return;
    }
  
    if ( httpHandler->isAuthorized )
        set_status_code( httpHandler, HTTP_200_OK );

    debug( "[HTTP_SERVER] %s : ReturnCode : %s (%d)\n", __FUNCTION__,
        httpHandler->returnCode,
        httpHandler->numberCode);
  
    if ( strcmp( extension,".html" ) == 0 || strcmp( extension,".htm" )  == 0 ) 
        strcpy( httpHandler->mimeType, "text/html; charset=UTF-8" );
    else if ( strcmp(extension,".css" ) == 0 )
       strcpy( httpHandler->mimeType, "text/css" );
    else if ( strcmp(extension,".png" ) == 0 )
       strcpy( httpHandler->mimeType, "image/png" );
    else if ( strcmp(extension,".jpg" ) == 0 || strcmp( extension,".jpeg" ) == 0 )
       strcpy( httpHandler->mimeType, "image/jpg");
    else if ( strcmp(extension,".gif" ) == 0 )
       strcpy( httpHandler->mimeType, "image/gif" );
    else if ( strcmp(extension,".ico" ) == 0 )
       strcpy( httpHandler->mimeType, "image/x-icon" );
    else 
    {
        set_status_code( httpHandler, HTTP_415_UNSUP_MEDIA_TYPE );
    } 
}

unsigned char * load_file( char * filename, unsigned long * len )
{
    unsigned char * buffer = NULL;
    char fullPath[128] = "";
    struct stat sb;
    FILE * f = NULL;

    snprintf( fullPath, sizeof( fullPath ), "%s%s", WEB_DIRECTORY, filename );

    if ( stat( fullPath, &sb ) < 0 )
        return NULL;
    else
        *len = sb.st_size;
    
    buffer = ( unsigned char * )Malloc( *len );
    memset( buffer, 0x00, *len );

    f = Fopen( fullPath, "r");
    if ( fread( buffer, sizeof( unsigned char ), *len, f ) < 0 )
    {
        Perror( "fread" );
        Free( buffer );
        buffer = NULL;
    }
    fclose( f );

    return buffer;
}

void set_status_code( http_t* httpHandler, int code )
{
    char * httpStrStatus = NULL;

    if ( ( httpStrStatus = str_status_code( code ) ) != NULL )
    { 
        httpHandler->numberCode = code;
    }
    else 
    {
        httpHandler->numberCode = HTTP_500_INTERNAL_ERROR;
    }
    httpHandler->returnCode = str_status_code( httpHandler->numberCode );
}

char * str_status_code( int code )
{
    unsigned int i;

    for ( i = 0; HttpStatusCodes[i].numberCode != 0; i++ )
        if ( HttpStatusCodes[i].numberCode == code )
            return HttpStatusCodes[i].textCode;

    /* Invalid or unsupported status code supplied */
    return NULL;
}

void secure_path ( http_t * httpHandler )
{
    if ( strstr ( httpHandler->path, ".." ) )
    {
        httpHandler->path="/error.html";
        httpHandler->isAuthorized = false;
        set_status_code( httpHandler, HTTP_400_BAD_REQUEST );
    }     
}  

FILE * Fopen_HTTP ( char * path, char * mod ) 
{
    char * newPath = NULL;
    FILE * retPtr = NULL;

    newPath = Malloc( sizeof( char ) * ( strlen( path ) + strlen( WEB_DIRECTORY ) + 1 ) );
    memset ( newPath, 0, sizeof( char ) * ( strlen( path ) + strlen( WEB_DIRECTORY ) + 1 ) );

    strcpy ( newPath, WEB_DIRECTORY );
    strcat ( newPath, path );

    debug ( "[HTTP_SERVER] Openning %s\n", newPath );

    retPtr = fopen( newPath, "r" );
    free( newPath );
    return retPtr;
}


char * url_decode( char * encoded )
{
    /*
     * This function decodes escaped strings (eg; the ones passed in POST)
     * it replaces every '+' by ' ' and convert %xx into ASCII chars
     * the decoded string' size is <= than the encoded's one
     */
    char * unescaped;

    /* replace '+' by spaces */
    unescaped = encoded;
    while ( *unescaped )
    {
        if ( *unescaped == '+' )
            *unescaped = ' ';
        unescaped++;
    }

    /* "real" unescape : change %xx by their ascii equivalents */
    unescaped = encoded;
    while ( *unescaped )
    {
        if ( *unescaped == '%' )
        {
            if ( isxdigit( *(unescaped+1) ) && isxdigit( *(unescaped+2) ) )
            {
                *unescaped = hex2dec_char( unescaped+1 );
                /* move the end of the string two chars on the left */
                rotate_string_left( unescaped+1, 2 );
            }
        }
        unescaped++;
    }

    *unescaped = '\0';

    return encoded;
}

char hex2dec_char( char * bytes )
{
    char array[3] = "";
    long r;

    array[0] = bytes[0];
    array[1] = bytes[1];

    r = strtol( array, NULL, 16 );

    return (char)r;
}

void rotate_string_left( char * str, unsigned int mov )
{
    char * p;

    /* EG for mov = 2 : convert "xxABCD" into "ABCD" */
    for ( p = str + mov; *(p - 1); p++ )
        *(p-mov) = *p;
}

int load_error( int errorType )
{
    FILE * fileReport, * fileCss;
    stats_t statTab;


    memset( &statTab, 0x00, sizeof( statTab ) );
    if ( errorType == PCAP_INVALID_FILTER )
        statTab.paramLine = "INVALID PCAP FILTER";
    else
        statTab.paramLine = "ERROR PAGE";


    fileReport = Fopen( WEB_DIRECTORY"/"REPORT_FILENAME, "w+" );
    fileCss    = Fopen( WEB_DIRECTORY"/style.css", "w+" );

    generate_html_toppage( fileReport, &statTab );
    generate_css( fileCss );

    switch ( errorType )
    {
        case    PCAP_EMPTY_FILE     :
            fprintf( fileReport, "\t<div id=\"title\">EMPTY PCAP FILE</div>" );
            break;

        case    PCAP_INVALID_FILTER :
            fprintf( fileReport, "\t<div id=\"title\">INVALID PCAP FILTER</div>" );
            break;

        default :
            fprintf( fileReport, "\t<div id=\"title\">%d : %s</div>",
                errorType,
                str_status_code( errorType ) );
            break;
    }

    end_html( fileReport );

    fclose( fileCss );
    fclose( fileReport );

    return 0;
}

